package com.nd.fastdp.security.jwt;

import com.nd.fastdp.framework.pojo.constant.RespCodeEnum;
import com.nd.fastdp.framework.pojo.vo.Result;
import com.nd.fastdp.security.cache.ShiroCache;
import com.nd.fastdp.security.properties.JwtProperties;
import com.nd.fastdp.security.service.ShiroService;
import com.nd.fastdp.security.util.JwtTokenUtil;
import com.nd.fastdp.security.util.JwtUtil;
import com.nd.fastdp.utils.HttpServletRequestUtil;
import com.nd.fastdp.utils.HttpServletResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Shiro JWT授权过滤器
 *
 * @author geekidea
 * @date 2019-09-27
 * @since 1.3.0.RELEASE
 **/
@Slf4j
public class JwtFilter extends AuthenticatingFilter {

    private ShiroService shiroService;

    private ShiroCache shiroCache;

    private JwtProperties jwtProperties;

    public JwtFilter(ShiroService shiroService, ShiroCache shiroCache, JwtProperties jwtProperties) {
        this.shiroService = shiroService;
        this.shiroCache = shiroCache;
        this.jwtProperties = jwtProperties;
    }

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = WebUtils.toHttp(request);
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpResponse.setHeader("Access-control-Allow-Origin", "*");
            httpResponse.setHeader("Access-Control-Allow-Methods", httpRequest.getMethod());
            httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers"));
            httpResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 将JWT Token包装成AuthenticationToken
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;

        String token = JwtTokenUtil.getToken();

        if (StringUtils.isBlank(token)) {

            if(HttpServletRequestUtil.isAjax()){
                HttpServletResponseUtil.printJson(response, Result.FAILD(RespCodeEnum.UNAUTHORIZED.getValue(), "token不能为空"));
            }else{
                request.setAttribute("errorMsg", "token不能为空");
                HttpServletRequestUtil.forward("/view/error/401", request, response);
            }

            //throw new TokenNotEmptyException("token不能为空");
        }

        if (!JwtUtil.isValid(token)) {

            if(HttpServletRequestUtil.isAjax()){
                HttpServletResponseUtil.printJson(response, Result.FAILD(RespCodeEnum.UNAUTHORIZED.getValue(), "Token无效"));
            }else{
                request.setAttribute("errorMsg", "Token无效");
                HttpServletRequestUtil.forward("/view/error/401", request, response);
            }

            //throw new TokenExpiredException("JWT Token已过期,token:" + token);
        }

        if (JwtUtil.isExpired(token)) {

            if(HttpServletRequestUtil.isAjax()){
                HttpServletResponseUtil.printJson(response, Result.FAILD(RespCodeEnum.UNAUTHORIZED.getValue(), "Token已过期"));
            }else{
                request.setAttribute("errorMsg", "Token已过期");
                HttpServletRequestUtil.forward("/view/error/401", request, response);
            }

            //throw new TokenExpiredException("JWT Token已过期,token:" + token);
        }

        // 如果开启redis二次校验，或者设置为单个用户token登录，则先在redis中判断token是否存在
        if (jwtProperties.isRedisCheck() || jwtProperties.isSingleLogin()) {
            boolean redisExpired = shiroCache.exists(token);
            if (!redisExpired) {

                if(HttpServletRequestUtil.isAjax()){
                    HttpServletResponseUtil.printJson(response, Result.FAILD(RespCodeEnum.UNAUTHORIZED.getValue(), "token无效"));
                }else{
                    request.setAttribute("errorMsg", "token无效");
                    HttpServletRequestUtil.forward("/view/error/401", request, response);
                }

                //throw new AuthenticationException("Redis Token不存在,token:" + token);
            }
        }

        String username = JwtUtil.getUsername(token);
        String salt;
        if (jwtProperties.isSaltCheck()){
            salt = shiroCache.getSalt(username);
        }else{
            salt = jwtProperties.getSecret();
        }
        return JwtToken.build(token, username, salt, jwtProperties.getExpireSecond());
    }

    /**
     * 访问失败处理
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        // 返回401
        /*httpServletResponse.setStatus(HttpStatus.OK.value());
        // 设置响应码为401或者直接输出消息
        String url = httpServletRequest.getRequestURI();
        log.error("onAccessDenied url：{}", url);
        Result result = Result.FAILD(RespCodeEnum.UNAUTHORIZED);
        HttpServletResponseUtil.printJson(httpServletResponse, result);*/
        return false;
    }

    /**
     * 判断是否允许访问
     *
     * @param servletRequest
     * @param servletResponse
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) {

        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;

        String url = WebUtils.toHttp(request).getRequestURI();
        log.debug("isAccessAllowed url:{}", url);
        if (this.isLoginRequest(request, response)) {
            return true;
        }
        boolean allowed = false;

        try {

            allowed = executeLogin(request, response);

        } catch (IllegalStateException e) { //not found any token

            log.error("Token不能为空", e);

            if(HttpServletRequestUtil.isAjax()){
                HttpServletResponseUtil.printJson(response, Result.FAILD(RespCodeEnum.UNAUTHORIZED.getValue(), "token无效"));
            }else{
                request.setAttribute("errorMsg", "token无效");
                HttpServletRequestUtil.forward("/view/error/401", request, response);
            }

        } catch (Exception e) {

            log.error("访问错误", e);

            if(HttpServletRequestUtil.isAjax()){
                HttpServletResponseUtil.printJson(response, Result.FAILD(RespCodeEnum.INTERNAL_SERVER_ERROR.getValue(), "系统异常"));
            }else{
                request.setAttribute("errorMsg", "系统异常");
                HttpServletRequestUtil.forward("/view/error/500", request, response);
            }
        }

        if(!allowed){

            if(HttpServletRequestUtil.isAjax()){
                HttpServletResponseUtil.printJson(response, Result.FAILD(RespCodeEnum.UNAUTHORIZED));
            }else{
                request.setAttribute("errorMsg", "认证失败");
                HttpServletRequestUtil.forward("/view/error/401", request, response);
            }
        }

        if(mappedValue != null && !super.isPermissive(mappedValue)){

            if(HttpServletRequestUtil.isAjax()){
                HttpServletResponseUtil.printJson(response, Result.FAILD(RespCodeEnum.NOT_PERMISSION));
            }else{
                HttpServletRequestUtil.forward("/view/error/403", request, response);
            }
        }



        return allowed && (mappedValue == null || super.isPermissive(mappedValue));
    }

    /**
     * 登录成功处理
     *
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        String url = WebUtils.toHttp(request).getRequestURI();
        log.debug("鉴权成功,token:{},url:{}", token, url);
        // 刷新token
        JwtToken jwtToken = (JwtToken) token;
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        shiroService.refreshToken(jwtToken, httpServletResponse);
        return true;
    }

    /**
     * 登录失败处理
     *
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        log.error("登录失败，token:" + token + ",error:" + e.getMessage(), e);
        return false;
    }
}
